package class

import (
	"gitee.com/microwww/jvm-go/jvm/classfile"
	"gitee.com/microwww/jvm-go/jvm/tool"
	"log"
)

type ClassLoader struct {
	path     *tool.Path
	classMap map[string]*Class
}

func NewClassLoader(path *tool.Path) *ClassLoader {
	cl := &ClassLoader{path, make(map[string]*Class)}
	cl.loadPrimitive()
	return cl
}

func (c *ClassLoader) DefineClass(className string) *Class {
	clz := c.classMap[className]
	if clz == nil {
		if className[0] == '[' {
			clz = c.loadArrayClass(className)
		} else {
			file := c.parseClassFile(className)
			clz = c.parseClass(file) //           // 1. 创建自己
		}
		c.classMap[className] = clz      // 先赋值, 防止递归死循环
		if len(clz.superClassName) > 0 { //       // 2. 先父类
			clz.superClass = c.DefineClass(clz.superClassName)
		}
		interfaces := make([]*Class, len(clz.interfacesName))
		for i, s := range clz.interfacesName { // // 3. 再接口
			interfaces[i] = c.DefineClass(s)
		}
		clz.interfaces = interfaces
		clz.initStaticVars() //                   // 4. 最后初始化自己
		for _, fun := range classListener[className] {
			fun(clz)
		}
	}
	return clz
}

func (c *ClassLoader) parseClassFile(name string) *classfile.ClassFile {
	bts, _ := c.readClassFile(name)
	return classfile.ParseClassFile(bts)
}

func (c *ClassLoader) readClassFile(name string) ([]byte, classfile.Entity) {
	bts, ety, ok := classfile.LookupClass(c.path, name)
	if ok != nil {
		panic("java.lang.ClassNotFoundException: " + name)
	}
	return bts, ety
}

func (c *ClassLoader) loadArrayClass(name string) *Class {
	v := &Class{
		access:         0x31, //public final
		name:           name,
		cp:             &ConstantPool{classLoader: c},
		superClassName: "java/lang/Object",
		interfacesName: []string{"java/lang/Cloneable", "java/io/Serializable"},
		staticVars:     map[string]interface{}{},
	}
	v.cp.class = v
	return v
}

func (c *ClassLoader) parseClass(classFile *classfile.ClassFile) *Class {
	clazz := c.newClass(classFile)
	log.Default().Printf("Loaded java class `%s`", clazz.name)
	return clazz
}

func (c *ClassLoader) loadPrimitive() {
	for name, innerType := range PRIMITIVE_TYPES {
		clz := &Class{
			access:     ACC_PUBLIC,
			name:       innerType,
			cp:         &ConstantPool{classLoader: c, consts: []Constant{}},
			staticVars: make(map[string]interface{}, 0),
			defValue:   PRIMITIVE_TYPES_DEF[name],
		}
		clz.cp.class = clz
		c.classMap[innerType] = clz
	}
}

func link(clazz *Class) {
	verify(clazz)
	prepare(clazz)
}

func verify(clazz *Class) {
}

func prepare(clazz *Class) {
}
